/*
-----------------------------------------------------------------------------
This file is part of NxGraphics, Multimedia Engine.
Author : Stephane Kyles. Developed in Prague, Czech Republic.
		_   __       ______                     __     _            
	   / | / /_  __ / ____/_____ ____ _ ____   / /_   (_)_____ _____
	  /  |/ /| |/_// / __ / ___// __ `// __ \ / __ \ / // ___// ___/
	 / /|  /_>  < / /_/ // /   / /_/ // /_/ // / / // // /__ (__  ) 
	/_/ |_//_/|_| \____//_/    \__,_// .___//_/ /_//_/ \___//____/  
									/_/                             
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#ifndef __NXGRAPHICS3DSHADOWS_H__
#define __NXGRAPHICS3DSHADOWS_H__

#include "NxGraphics_Prerequisites.h"
#include "..\Dependencies\OGRE\SDK\include\OGRE\OgreCustomCompositionPass.h"
#include "..\Dependencies\OGRE\SDK\include\OGRE\OgreCompositorLogic.h"

namespace NxGraphics_Namespace {

/** Caching, on-the-fly material generator. This is a class that automatically 
	generates and stores different permutations of a material, and its shaders. 
	It can be used if you have a material that has lots of slightly different 
	variations, like whether to use a specular light, skinning, normal mapping 
	and other options. Writing all these out is a tedious job. Of course it is 
	possible to always use the material with all features, but that might result 
	in large, slow shader programs. This class provides an efficient solution
	to that.
 */
class MaterialGenerator
{
public:
	/** Bitfield used to signify a material permutations */
	typedef Ogre::uint32 Perm;
	virtual ~MaterialGenerator();

	const Ogre::MaterialPtr &getMaterial(Perm permutation);

	/** Implementation class that takes care of actual generation or lookup
		of the various constituent parts (template material, fragment shader
		and vertex shader). These methods are only called once for every permutation,
		after which the result is stored and re-used.
	*/
	class Impl
	{
	public:
		virtual ~Impl();
		virtual Ogre::GpuProgramPtr generateVertexShader(Perm permutation) = 0;
		virtual Ogre::GpuProgramPtr generateFragmentShader(Perm permutation) = 0;
		virtual Ogre::MaterialPtr generateTemplateMaterial(Perm permutation) = 0;
	};
protected:
	/** The constructor is protected as this base class should never be constructed
		as-is. It is meant to be subclassed so that values can be assigned to
		the various fields controlling material generator, and most importantly, the
		mImpl field.
	*/
	MaterialGenerator();

	const Ogre::GpuProgramPtr &getVertexShader(Perm permutation);
	const Ogre::GpuProgramPtr &getFragmentShader(Perm permutation);
	const Ogre::MaterialPtr &getTemplateMaterial(Perm permutation);
	
	/// Base name of materials generated by this
	Ogre::String materialBaseName;
	/// Mask of permutation bits that influence vertex shader choice
	Perm vsMask;
	/// Mask of permutation bits that influence fragment shader choice
	Perm fsMask;
	/// Mask of permutation bits that influence template material choice
	Perm matMask;
	/// Generator
	Impl *mImpl;

	typedef Ogre::map<Perm, Ogre::GpuProgramPtr>::type ProgramMap;
	typedef Ogre::map<Perm, Ogre::MaterialPtr>::type MaterialMap;

	ProgramMap mVs, mFs;
	MaterialMap mTemplateMat, mMaterials;
};

class LightMaterialGenerator: public MaterialGenerator
{
public:
	/** Permutation of light materials
	 */
	enum MaterialID
	{
		MI_POINT			= 0x01, // Is a point light
		MI_SPOTLIGHT		= 0x02, // Is a spot light
        MI_DIRECTIONAL		= 0x04, // Is a directional light
		
		MI_ATTENUATED		= 0x08, // Rendered attenuated
		MI_SPECULAR			= 0x10, // Specular component is calculated
		MI_SHADOW_CASTER	= 0x20	// Will cast shadows
	};

	LightMaterialGenerator();
	virtual ~LightMaterialGenerator();
};

class GeomUtils
{
public:
	// Create a sphere Mesh with a given name, radius, number of rings and number of segments
	static void createSphere(const Ogre::String& strName
		, float radius
		, int nRings, int nSegments
		, bool bNormals
		, bool bTexCoords
		);


	// Fill up a fresh copy of VertexData and IndexData with a sphere's coords given the number of rings and the number of segments
	static void createSphere(Ogre::VertexData*& vertexData, Ogre::IndexData*& indexData
		, float radius
		, int nRings, int nSegments
		, bool bNormals
		, bool bTexCoords);

	// Create a cone Mesh with a given name, radius and number of vertices in base
	// Created cone will have its head at 0,0,0, and will 'expand to' positive y
	static void createCone(const Ogre::String& strName
		, float radius
		, float height
		, int nVerticesInBase);

	// Fill up a fresh copy of VertexData and IndexData with a cone's coords given the radius and number of vertices in base
	static void createCone(Ogre::VertexData*& vertexData, Ogre::IndexData*& indexData
		, float radius
		, float height
		, int nVerticesInBase);


	// Fill up a fresh copy of VertexData with a normalized quad
	static void createQuad(Ogre::VertexData*& vertexData);


};




// Renderable for rendering Ambient component and also to
// establish the depths

// Just instantiation is sufficient
// Note that instantiation is necessary to at least establish the depths
// even if the current ambient colour is 0

// its ambient colour is same as the scene's ambient colour

// XXX Could make this a singleton/make it private to the DeferredShadingSystem e.g.

class AmbientLight : public Ogre::SimpleRenderable

{
public:
	AmbientLight();
	~AmbientLight();

	/** @copydoc MovableObject::getBoundingRadius */
	virtual Ogre::Real getBoundingRadius(void) const;
	/** @copydoc Renderable::getSquaredViewDepth */
	virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera*) const;
	/** @copydoc Renderable::getMaterial */
	virtual const Ogre::MaterialPtr& getMaterial(void) const;

	virtual void getWorldTransforms(Ogre::Matrix4* xform) const;

	void updateFromCamera(Ogre::Camera* camera);
protected:
	Ogre::Real mRadius;
	Ogre::MaterialPtr mMatPtr;
};

/** Deferred light geometry. Each instance matches a normal light.
	Should not be created by the user.
	XXX support other types of light other than point lights.
 */
class DLight: public Ogre::SimpleRenderable
{
public:
	DLight( MaterialGenerator *gen, Ogre::Light* parentLight);
	~DLight();

	/** Update the information from the light that matches this one 
	 */
	void updateFromParent();

	/** Update the information that is related to the camera
	 */
	void updateFromCamera(Ogre::Camera* camera);

	/** Does this light cast shadows?
	*/
	virtual bool getCastChadows() const;

	/** @copydoc MovableObject::getBoundingRadius */
	virtual Ogre::Real getBoundingRadius(void) const;
	/** @copydoc Renderable::getSquaredViewDepth */
	virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera*) const;
	/** @copydoc Renderable::getMaterial */
	virtual const Ogre::MaterialPtr& getMaterial(void) const;
	/** @copydoc Renderable::getBoundingRadius */
	virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
protected:

	/** Check if the camera is inside a light
	*/
	bool isCameraInsideLight(Ogre::Camera* camera);

	/** Create geometry for this light.
	*/
	void rebuildGeometry(float radius);

	/** Create a sphere geometry.
	*/
	void createSphere(float radius, int nRings, int nSegments);

	/** Create a rectangle.
	*/
	void createRectangle2D();
	
	/** Create a cone.
	*/
	void createCone(float radius, float height, int nVerticesInBase);

	/** Set constant, linear, quadratic Attenuation terms 
	 */
	void setAttenuation(float c, float b, float a);

	/** Set the specular colour
	 */
	void setSpecularColour(const Ogre::ColourValue &col);

	/// The light that this DLight renders
	Ogre::Light* mParentLight;
	/// Mode to ignore world orientation/position
	bool bIgnoreWorld;
	/// Bounding sphere radius
	float mRadius;
	/// Deferred shading system this minilight is part of
	MaterialGenerator *mGenerator;
	/// Material permutation
	Ogre::uint32 mPermutation;
};


 //The render operation that will be called each frame in the custom composition pass
//This is the class that will send the actual render calls of the spheres (point lights),
//cones (spotlights) and quads (directional lights) after the GBuffer has been constructed
class DeferredLightRenderOperation : public Ogre::CompositorInstance::RenderSystemOperation
{
public:
	DeferredLightRenderOperation(Ogre::CompositorInstance* instance, const Ogre::CompositionPass* pass);
	
	/** @copydoc CompositorInstance::RenderSystemOperation::execute */
	virtual void execute(Ogre::SceneManager *sm, Ogre::RenderSystem *rs);

	virtual ~DeferredLightRenderOperation();
private:

	/** Create a new deferred light
	 */
	DLight *createDLight(Ogre::Light* light);
	
	//The texture names of the GBuffer components
	Ogre::String mTexName0;
	Ogre::String mTexName1;

	//The material generator for the light geometry
	MaterialGenerator* mLightMaterialGenerator;

	//The map of deferred light geometries already constructed
	typedef std::map<Ogre::Light*, DLight*> LightsMap;
	LightsMap mLights;

	//The ambient light used to render the scene
	AmbientLight* mAmbientLight;

	//The viewport that we are rendering to
	Ogre::Viewport* mViewport;
};

//The custom composition pass that is used for rendering the light geometry
//This class needs to be registered with the CompositorManager
class DeferredLightCompositionPass : public Ogre::CustomCompositionPass
{
public:

	/** @copydoc CustomCompositionPass::createOperation */
	virtual Ogre::CompositorInstance::RenderSystemOperation* createOperation(
		Ogre::CompositorInstance* instance, const Ogre::CompositionPass* pass)
	{
		return OGRE_NEW DeferredLightRenderOperation(instance, pass);
	}

protected:
	virtual ~DeferredLightCompositionPass() {}
};

//The simple types of compositor logics will all do the same thing -
//Attach a listener to the created compositor
class ListenerFactoryLogic : public Ogre::CompositorLogic
{
public:
	/** @copydoc CompositorLogic::compositorInstanceCreated */
	virtual void compositorInstanceCreated(Ogre::CompositorInstance* newInstance) 
	{
		Ogre::CompositorInstance::Listener* listener = createListener(newInstance);
		newInstance->addListener(listener);
		mListeners[newInstance] = listener;
	}
	
	/** @copydoc CompositorLogic::compositorInstanceDestroyed */
	virtual void compositorInstanceDestroyed(Ogre::CompositorInstance* destroyedInstance)
	{
		delete mListeners[destroyedInstance];
		mListeners.erase(destroyedInstance);
	}

protected:
	//This is the method that implementers will need to create
	virtual Ogre::CompositorInstance::Listener* createListener(Ogre::CompositorInstance* instance) = 0;
private:
	typedef std::map<Ogre::CompositorInstance*, Ogre::CompositorInstance::Listener*> ListenerMap;
	ListenerMap mListeners;

};

class SSAOLogic : public ListenerFactoryLogic
{
protected:
	/** @copydoc ListenerFactoryLogic::createListener */
	virtual Ogre::CompositorInstance::Listener* createListener(Ogre::CompositorInstance* instance);
};


/** Class for skipping materials which do not have the scheme defined
 */
class NullSchemeHandler : public Ogre::MaterialManager::Listener
{
public:
	/** @copydoc MaterialManager::Listener::handleSchemeNotFound */
	virtual Ogre::Technique* handleSchemeNotFound(unsigned short schemeIndex, 
		const Ogre::String& schemeName, Ogre::Material* originalMaterial, unsigned short lodIndex, 
		const Ogre::Renderable* rend)
	{
		//Creating a technique so the handler only gets called once per material
		Ogre::Technique * emptyTech = originalMaterial->createTechnique();
		emptyTech->removeAllPasses();
		emptyTech->setSchemeName(schemeName);
		return emptyTech;
	}
};





/** Class for generating materials for objects to render themselves to the GBuffer
 *  @note This does not support all the possible rendering techniques out there.
 *  in order to support more, either expand this class or make sure that objects
 *  that will not get treated correctly will not have materials generated for them.
 */
class GBufferMaterialGenerator : public MaterialGenerator
{
public:
	
	//Constructor
	GBufferMaterialGenerator();

	//The relevant options for objects that are rendered to the GBuffer
	enum GBufferPermutations 
	{
		//(Regular) Textures
		GBP_NO_TEXTURES =			0x00000000,
		GBP_ONE_TEXTURE =			0x00000001,
		GBP_TWO_TEXTURES =			0x00000002,
		GBP_THREE_TEXTURES =		0x00000003,
		GBP_TEXTURE_MASK =			0x0000000F,
		
        //Material properties
        GBP_HAS_DIFFUSE_COLOUR =     0x00000010,

		//The number of texture coordinate sets
		GBP_NO_TEXCOORDS =			0x00000000,
		GBP_ONE_TEXCOORD =			0x00000100,
		GBP_TWO_TEXCOORDS =			0x00000200,
		GBP_TEXCOORD_MASK =			0x00000700,

		//Do we have a normal map
		GBP_NORMAL_MAP =			0x00000800,

		//Are we skinned?
		GBP_SKINNED =				0x00010000
	};
	
	//The mask of the flags that matter for generating the fragment shader
	static const Ogre::uint32 FS_MASK =		0x0000FFFF;
	
	//The mask of the flags that matter for generating the vertex shader
	static const Ogre::uint32 VS_MASK =		0x00FFFF00;
	
	//The mask of the flags that matter for generating the material
	static const Ogre::uint32 MAT_MASK =	0xFF00FFFF;
};


class GBufferSchemeHandler : public Ogre::MaterialManager::Listener
{
public:
	/** @copydoc MaterialManager::Listener::handleSchemeNotFound */
	virtual Ogre::Technique* handleSchemeNotFound(unsigned short schemeIndex, 
		const Ogre::String& schemeName, Ogre::Material* originalMaterial, unsigned short lodIndex, 
		const Ogre::Renderable* rend);
protected:
	//The material generator
	GBufferMaterialGenerator mMaterialGenerator;
	
	//The string that will be checked in textures to determine whether they are normal maps
	static const Ogre::String NORMAL_MAP_PATTERN;

	//A structure for containing the properties of a material, relevant to GBuffer rendering
	//You might need to expand this class to support more options
	struct PassProperties 
	{
		PassProperties() : isDeferred(true), normalMap(0), isSkinned(false) {}

		bool isDeferred;
		Ogre::vector<Ogre::TextureUnitState*>::type regularTextures;
		Ogre::TextureUnitState* normalMap;
		bool isSkinned;
        bool hasDiffuseColour;
		
		//Example of possible extension : vertex colours
		//Ogre::TrackVertexColourType vertexColourType;
	};

	//Inspect a technique and return its relevant properties
	PassProperties inspectPass(Ogre::Pass* pass, 
		unsigned short lodIndex, const Ogre::Renderable* rend);

	//Get the permutation of material flags that fit a certain property sheet
	MaterialGenerator::Perm getPermutation(const PassProperties& props);

	//Fill a pass with the specific data from the pass it is based on
	void fillPass(Ogre::Pass* gBufferPass, Ogre::Pass* originalPass, const PassProperties& props);

	//Check if a texture is a normal map, and fill property sheet accordingly
	bool checkNormalMap(Ogre::TextureUnitState* tus, PassProperties& props);
};


/** System to manage Deferred Shading for a camera/render target.
 *  @note With the changes to the compositor framework, this class just
 *		selects which compositors to enable.
 */
class DeferredShadingSystem : public Ogre::RenderTargetListener
{
public:
	DeferredShadingSystem(Ogre::Viewport *vp, Ogre::SceneManager *sm, Ogre::Camera *cam);
	~DeferredShadingSystem();

	enum DSMode
	{
		DSM_SHOWLIT = 0,     // The deferred shading mode
		DSM_SHOWCOLOUR = 1,  // Show diffuse (for debugging)
		DSM_SHOWNORMALS = 2, // Show normals (for debugging)
		DSM_SHOWDSP = 3,	 // Show depth and specular channel (for debugging)
		DSM_COUNT = 4
	};

	//The first render queue that does get rendered into the GBuffer
	//place objects (like skies) that should be before gbuffer before this one.
	static const Ogre::uint8 PRE_GBUFFER_RENDER_QUEUE;
	
	//The first render queue that does not get rendered into the GBuffer
	//place transparent (or other non gbuffer) objects after this one
	static const Ogre::uint8 POST_GBUFFER_RENDER_QUEUE;

	void initialize();

	/** Set rendering mode (one of DSMode)
	 */
	void setMode(DSMode mode);

	DSMode getMode(void) const;

	/** Set screen space ambient occlusion mode
	 */
	void setSSAO(bool ssao);
	
	bool getSSAO() const;

	/** Activate or deactivate system
	 */
	void setActive(bool active);
	
protected:
	Ogre::Viewport *mViewport;
	Ogre::SceneManager *mSceneMgr;
	Ogre::Camera *mCamera;
	
	Ogre::CompositorInstance *mGBufferInstance;
	// Filters
	Ogre::CompositorInstance *mInstance[DSM_COUNT];
	Ogre::CompositorInstance* mSSAOInstance;
	// Active/inactive
	bool mActive;
	DSMode mCurrentMode;
	bool mSSAO;

	void createResources();
	
	void logCurrentMode(void);
};

}

#endif